package com.pokermind.operator;

import com.pokermind.common.log.PlainLog;
import com.pokermind.common.log.TimeCostLog;
import com.pokermind.model.po.PokerHand;
import com.pokermind.model.po.PokerHandAction;
import com.pokermind.repository.dao.PokerHandDao;
import com.pokermind.repository.dao.PokerHandActionDao;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.List;
import java.util.Optional;

/**
 * 扑克手牌业务操作类
 * 处理手牌相关的业务逻辑
 * 
 * @author PokerMind Team
 */
@Service
@Transactional
public class PokerHandOperator {
    
    private static final Logger logger = LoggerFactory.getLogger(PokerHandOperator.class);
    
    @Autowired
    private PokerHandDao pokerHandDao;
    
    @Autowired
    private PokerHandActionDao pokerHandActionDao;
    
    @Autowired
    private PokerUserOperator pokerUserOperator;
    
    /**
     * 创建新手牌
     * @param hand 手牌对象
     * @return 创建的手牌
     */
    public PokerHand createHand(PokerHand hand) {
        PlainLog plainLog = new TimeCostLog("op", "createHand")
            .put("handId", hand != null ? hand.getHandId() : null)
            .put("userId", hand != null ? hand.getUserId() : null)
            .put("tableId", hand != null ? hand.getTableId() : null);
            
        try {
            // 参数验证
            if (hand == null) {
                throw new IllegalArgumentException("Hand cannot be null");
            }
            if (hand.getHandId() == null || hand.getHandId().trim().isEmpty()) {
                throw new IllegalArgumentException("Hand ID cannot be null or empty");
            }
            if (hand.getUserId() == null) {
                throw new IllegalArgumentException("User ID cannot be null");
            }
            
            plainLog.put("heroSeat", hand.getHeroSeatNumber())
                .put("heroPosition", hand.getHeroPosition())
                .put("playersDealt", hand.getPlayersDealt());
            
            // 检查是否已存在相同的手牌记录
            Optional<PokerHand> existingHand = pokerHandDao.findByHandIdAndUserId(hand.getHandId(), hand.getUserId());
            if (existingHand.isPresent()) {
                plainLog.put("existingHandId", existingHand.get().getId());
                throw new IllegalArgumentException("Hand already exists: " + hand.getHandId() + " for user: " + hand.getUserId());
            }
            
            // 保存手牌
            PokerHand savedHand = pokerHandDao.save(hand);
            plainLog.put("savedHandId", savedHand.getId()).put("op_rslt", "done");
            
            logger.info(plainLog.toString());
            return savedHand;
            
        } catch (IllegalArgumentException e) {
            logger.warn(plainLog.put("op_rslt", "param_error:" + e.getMessage()).toString());
            throw e;
        } catch (Exception e) {
            logger.error(plainLog.put("op_rslt", "ex:" + e.getMessage()).toString(), e);
            throw new RuntimeException("Failed to create hand", e);
        }
    }
    
    /**
     * 根据ID查找手牌
     * @param handId 手牌ID
     * @return 手牌对象，如果不存在返回empty
     */
    @Transactional(readOnly = true)
    public Optional<PokerHand> findHandById(Long handId) {
        return pokerHandDao.findById(handId);
    }
    
    /**
     * 根据手牌ID和用户ID查找手牌
     * @param handId 手牌ID
     * @param userId 用户ID
     * @return 手牌对象，如果不存在返回empty
     */
    @Transactional(readOnly = true)
    public Optional<PokerHand> findHandByHandIdAndUserId(String handId, Long userId) {
        return pokerHandDao.findByHandIdAndUserId(handId, userId);
    }
    
    /**
     * 获取用户的手牌历史
     * @param userId 用户ID
     * @param limit 返回数量限制
     * @return 手牌列表
     */
    @Transactional(readOnly = true)
    public List<PokerHand> getUserHandHistory(Long userId, int limit) {
        return pokerHandDao.findByUserId(userId, limit);
    }
    
    /**
     * 获取用户在指定时间范围内的手牌
     * @param userId 用户ID
     * @param startTime 开始时间
     * @param endTime 结束时间
     * @return 手牌列表
     */
    @Transactional(readOnly = true)
    public List<PokerHand> getUserHandsInTimeRange(Long userId, LocalDateTime startTime, LocalDateTime endTime) {
        // 将 LocalDateTime 转换为毫秒时间戳
        Long startTimeMillis = startTime != null ? startTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() : null;
        Long endTimeMillis = endTime != null ? endTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() : null;
        return pokerHandDao.findByUserIdAndTimeRange(userId, startTimeMillis, endTimeMillis);
    }
    
    /**
     * 获取用户在指定位置的手牌
     * @param userId 用户ID
     * @param position 位置
     * @param limit 返回数量限制
     * @return 手牌列表
     */
    @Transactional(readOnly = true)
    public List<PokerHand> getUserHandsByPosition(Long userId, String position, int limit) {
        return pokerHandDao.findByUserIdAndPosition(userId, position, limit);
    }
    
    /**
     * 获取未完成的手牌
     * @param userId 用户ID（可选）
     * @return 未完成的手牌列表
     */
    @Transactional(readOnly = true)
    public List<PokerHand> getIncompleteHands(Long userId) {
        return pokerHandDao.findIncompleteHands(userId);
    }
    
    /**
     * 完成手牌
     * @param handId 手牌数据库ID
     * @param netResult 净盈亏
     * @param finalAction 最后行动
     * @return 更新的行数
     */
    public int completeHand(Long handId, BigDecimal netResult, String finalAction) {
        PlainLog plainLog = new TimeCostLog("op", "completeHand")
            .put("handId", handId)
            .put("netResult", netResult)
            .put("finalAction", finalAction);
            
        try {
            // 参数验证
            if (handId == null) {
                throw new IllegalArgumentException("Hand ID cannot be null");
            }
            
            // 更新手牌结果
            int updated = pokerHandDao.updateHandResult(handId, netResult, finalAction);
            plainLog.put("handUpdated", updated);
            
            if (updated > 0) {
                // 更新用户统计信息
                Optional<PokerHand> handOpt = pokerHandDao.findById(handId);
                if (handOpt.isPresent()) {
                    PokerHand hand = handOpt.get();
                    plainLog.put("userId", hand.getUserId())
                        .put("gameHandId", hand.getHandId());
                    pokerUserOperator.updateUserStats(hand.getUserId(), netResult);
                    plainLog.put("userStatsUpdated", true);
                } else {
                    plainLog.put("userStatsUpdated", false).put("reason", "hand_not_found_after_update");
                }
            }
            
            plainLog.put("op_rslt", "done");
            logger.info(plainLog.toString());
            return updated;
            
        } catch (IllegalArgumentException e) {
            logger.warn(plainLog.put("op_rslt", "param_error:" + e.getMessage()).toString());
            throw e;
        } catch (Exception e) {
            logger.error(plainLog.put("op_rslt", "ex:" + e.getMessage()).toString(), e);
            throw new RuntimeException("Failed to complete hand", e);
        }
    }
    
    /**
     * 更新手牌信息
     * @param hand 手牌对象
     * @return 更新的行数
     */
    public int updateHand(PokerHand hand) {
        return pokerHandDao.update(hand);
    }
    
    /**
     * 添加手牌行动
     * @param action 行动对象
     * @return 保存的行动
     */
    public PokerHandAction addHandAction(PokerHandAction action) {
        PlainLog plainLog = new TimeCostLog("op", "addHandAction")
            .put("handId", action != null ? action.getHandId() : null)
            .put("seatNumber", action != null ? action.getSeatNumber() : null)
            .put("actionType", action != null ? action.getActionType() : null)
            .put("amount", action != null ? action.getAmount() : null);
            
        try {
            // 参数验证
            if (action == null) {
                throw new IllegalArgumentException("Action cannot be null");
            }
            if (action.getHandId() == null) {
                throw new IllegalArgumentException("Hand ID cannot be null");
            }
            if (action.getActionType() == null || action.getActionType().trim().isEmpty()) {
                throw new IllegalArgumentException("Action type cannot be null or empty");
            }
            
            plainLog.put("street", action.getStreet())
                .put("globalSequence", action.getGlobalSequence())
                .put("actionSequence", action.getActionSequence());
            
            // 保存行动
            PokerHandAction savedAction = pokerHandActionDao.save(action);
            plainLog.put("savedActionId", savedAction.getId()).put("op_rslt", "done");
            
            logger.info(plainLog.toString());
            return savedAction;
            
        } catch (IllegalArgumentException e) {
            logger.warn(plainLog.put("op_rslt", "param_error:" + e.getMessage()).toString());
            throw e;
        } catch (Exception e) {
            logger.error(plainLog.put("op_rslt", "ex:" + e.getMessage()).toString(), e);
            throw new RuntimeException("Failed to add hand action", e);
        }
    }
    
    /**
     * 批量添加手牌行动
     * @param actions 行动列表
     * @return 保存的行动数量
     */
    public int addHandActions(List<PokerHandAction> actions) {
        PlainLog plainLog = new TimeCostLog("op", "addHandActions")
            .put("actionCount", actions != null ? actions.size() : 0);
            
        try {
            // 参数验证
            if (actions == null || actions.isEmpty()) {
                throw new IllegalArgumentException("Actions list cannot be null or empty");
            }
            
            // 获取第一个行动的信息用于日志
            PokerHandAction firstAction = actions.get(0);
            plainLog.put("handId", firstAction.getHandId())
                .put("firstActionType", firstAction.getActionType())
                .put("street", firstAction.getStreet());
            
            // 验证所有行动属于同一手牌
            Long handId = firstAction.getHandId();
            for (PokerHandAction action : actions) {
                if (!handId.equals(action.getHandId())) {
                    throw new IllegalArgumentException("All actions must belong to the same hand");
                }
                if (action.getActionType() == null || action.getActionType().trim().isEmpty()) {
                    throw new IllegalArgumentException("Action type cannot be null or empty");
                }
            }
            
            // 批量保存
            int savedCount = pokerHandActionDao.batchSave(actions);
            plainLog.put("savedCount", savedCount).put("op_rslt", "done");
            
            logger.info(plainLog.toString());
            return savedCount;
            
        } catch (IllegalArgumentException e) {
            logger.warn(plainLog.put("op_rslt", "param_error:" + e.getMessage()).toString());
            throw e;
        } catch (Exception e) {
            logger.error(plainLog.put("op_rslt", "ex:" + e.getMessage()).toString(), e);
            throw new RuntimeException("Failed to add hand actions", e);
        }
    }
    
    /**
     * 获取手牌的所有行动
     * @param handId 手牌ID
     * @return 行动列表
     */
    @Transactional(readOnly = true)
    public List<PokerHandAction> getHandActions(Long handId) {
        return pokerHandActionDao.findByHandId(handId);
    }
    
    /**
     * 获取手牌在指定街道的行动
     * @param handId 手牌ID
     * @param street 街道
     * @return 行动列表
     */
    @Transactional(readOnly = true)
    public List<PokerHandAction> getHandActionsByStreet(Long handId, String street) {
        return pokerHandActionDao.findByHandIdAndStreet(handId, street);
    }
    
    /**
     * 获取手牌中指定座位的行动
     * @param handId 手牌ID
     * @param seatNumber 座位号
     * @return 行动列表
     */
    @Transactional(readOnly = true)
    public List<PokerHandAction> getHandActionsBySeat(Long handId, Integer seatNumber) {
        return pokerHandActionDao.findByHandIdAndSeatNumber(handId, seatNumber);
    }
    
    /**
     * 获取手牌的最后一个行动
     * @param handId 手牌ID
     * @return 最后一个行动，如果不存在返回null
     */
    @Transactional(readOnly = true)
    public PokerHandAction getLastHandAction(Long handId) {
        return pokerHandActionDao.findLastActionByHandId(handId);
    }
    
    /**
     * 删除手牌及其所有相关数据
     * @param handId 手牌ID
     * @return 删除的行数
     */
    public int deleteHand(Long handId) {
        // 先删除相关的行动记录
        pokerHandActionDao.deleteByHandId(handId);
        
        // 再删除手牌记录
        return pokerHandDao.deleteById(handId);
    }
    
    /**
     * 统计用户的手牌数量
     * @param userId 用户ID
     * @return 手牌总数
     */
    @Transactional(readOnly = true)
    public long countUserHands(Long userId) {
        return pokerHandDao.countByUserId(userId);
    }
    
    /**
     * 统计用户在指定时间范围内的手牌数量
     * @param userId 用户ID
     * @param startTime 开始时间
     * @param endTime 结束时间
     * @return 手牌数量
     */
    @Transactional(readOnly = true)
    public long countUserHandsInTimeRange(Long userId, LocalDateTime startTime, LocalDateTime endTime) {
        // 将 LocalDateTime 转换为毫秒时间戳
        Long startTimeMillis = startTime != null ? startTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() : null;
        Long endTimeMillis = endTime != null ? endTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() : null;
        return pokerHandDao.countByUserIdAndTimeRange(userId, startTimeMillis, endTimeMillis);
    }
    
    /**
     * 获取用户最新的手牌
     * @param userId 用户ID
     * @return 最新的手牌，如果不存在返回empty
     */
    @Transactional(readOnly = true)
    public Optional<PokerHand> getLatestUserHand(Long userId) {
        return pokerHandDao.findLatestByUserId(userId);
    }
    
    /**
     * 检查手牌是否存在
     * @param handId 手牌数据库ID
     * @return true如果存在，false如果不存在
     */
    @Transactional(readOnly = true)
    public boolean handExists(Long handId) {
        return pokerHandDao.existsById(handId);
    }
    
    /**
     * 检查手牌是否存在（通过手牌ID和用户ID）
     * @param handId 手牌ID
     * @param userId 用户ID
     * @return true如果存在，false如果不存在
     */
    @Transactional(readOnly = true)
    public boolean handExists(String handId, Long userId) {
        return pokerHandDao.findByHandIdAndUserId(handId, userId).isPresent();
    }
}